package csel.model;

import java.lang.IllegalArgumentException;

import csel.model.areaeffect.AreaEffect;
import csel.model.entity.Entity;
import csel.model.items.Item;

public class Map implements java.io.Serializable
{
	private static final long serialVersionUID = 8L;
	Tile[][] tileMatrix;
	private Tile[][] lastCalculatedViewableTiles;
	private Tile lastCenterTile;
	private final int startX;
	private final int startY;
	private int lastDx, lastDy;

	public Map(Tile[][] tileMatrix, int startX, int startY)
	{
		this.tileMatrix = tileMatrix;
		
		this.startX = startX;
		this.startY = startY;
	}

	/**
	 * Attach all necessary map objects with specified GameGlobals.
	 */
	void attach(GameGlobals globals)
	{
		Tile[][] tiles = getTileMatrix();
		AttachVisitor av = new AttachVisitor(globals);
		
		for (int i = 0; i < tiles.length; i++)
		{
			for (Tile t : tiles[i])
			{
				if (t == null)
					continue;

				t.accept(av);
			}
		}
	}

	// return tiles
	public Tile getTile(int x, int y)
	{
		try
		{
			return tileMatrix[x][y];
		} catch (ArrayIndexOutOfBoundsException e)
		{
			return null;
		}
	}

	public Tile[][] getAdjacentTiles(Tile tile, int dx, int dy)
	{
		if ((tile == lastCenterTile) && (lastCalculatedViewableTiles != null)
				&& (dx == lastCalculatedViewableTiles.length) && (dx > 0)
				&& (dy == lastCalculatedViewableTiles[0].length))
		{
			return (lastCalculatedViewableTiles);
		} else
		{
			return (calculateViewableTiles(tile, dx, dy));
		}

	}

	public Tile[][] getTileMatrix()
	{
		return tileMatrix;
	}

	public void setTileMatrix(Tile[][] tileMatrix)
	{
		this.tileMatrix = tileMatrix;
	}

	private Tile[][] calculateViewableTiles(Tile tile, int dx, int dy)
	{
		lastDx = dx;
		lastDy = dy;
		lastCenterTile = tile;
		int dxLength = 2 * Math.abs(dx);
		int dyLength = 2 * Math.abs(dy);
		Location locTemp = tile.getLocation();
		if (locTemp == null)
		{
			return null;
		}

		int xTile = locTemp.getX();
		int yTile = locTemp.getY();
		RealTile[][] newTile = new RealTile[dxLength + 1][dyLength + 1];

		// logic for getting the coordinates translated. if below dx or dy then
		// it
		// is subtracted a right amount, if above dx or dy then it is added the
		// right amount
		for (int i = 0; i <= dxLength; i++)
		{
			for (int j = 0; j <= dyLength; j++)
			{
				int xTemp = xTile - (dx - i);
				int yTemp = yTile - (dy - j);
				try
				{
					newTile[i][j] = (RealTile)tileMatrix[xTemp][yTemp];
				} catch (ArrayIndexOutOfBoundsException e)
				{
				}
			}
		}
		lastCalculatedViewableTiles = newTile;
		return newTile;
	}

	public Tile getStartingTile()
	{
		return (getTile(startX, startY));
	}

	private class AttachVisitor implements TileVisitor
	{
		GameGlobals globals;
		
		public AttachVisitor(GameGlobals gg)
		{
			this.globals = gg;
		}

		public void visitTerrain(Terrain t)
		{
			/* terrains don't need attach() */
		}
		
		public void visitDecal(Decal d)
		{
			/* decals don't need attach() */
		}

		public void visitItem(Item i)
		{
			/* items don't need attach() */
		}

		public void visitAreaEffect(AreaEffect ae)
		{
			ae.attach(globals);
		}

		public void visitEntity(Entity e)
		{
			e.attach(globals);
		}

		public void visitProjectile(Projectile proj)
		{
			proj.attach(globals);
		}
	}
}
